﻿using System;
using System.Collections.Generic;
using UnityEngine;
using FairyGUI.Utils;

namespace FairyGUI
{
	/// <summary>
	/// 
	/// </summary>
	[ExecuteInEditMode]
	[AddComponentMenu("FairyGUI/UI Painter")]
	[RequireComponent(typeof(MeshCollider), typeof(MeshRenderer))]
	public class UIPainter : MonoBehaviour, EMRenderTarget
	{
		/// <summary>
		/// 
		/// </summary>
		public Container container { get; private set; }

		/// <summary>
		/// 
		/// </summary>
		public string packageName;

		/// <summary>
		/// 
		/// </summary>
		public string componentName;

		/// <summary>
		/// 
		/// </summary>
		public int sortingOrder;

		[SerializeField]
		string packagePath;
		[SerializeField]
		Camera renderCamera = null;
		[SerializeField]
		bool fairyBatching = false;
		[SerializeField]
		bool touchDisabled = false;

		GComponent _ui;
		[NonSerialized]
		bool _created;
		[NonSerialized]
		bool _captured;
		[NonSerialized]
		Renderer _renderer;

		[NonSerialized]
		RenderTexture _texture;

		EventCallback0 _captureDelegate; 

		void OnEnable()
		{
			if (Application.isPlaying)
			{
				if (this.container == null)
				{
					CreateContainer();

					if (!string.IsNullOrEmpty(packagePath) && UIPackage.GetByName(packageName) == null)
						UIPackage.AddPackage(packagePath);
				}
				else
					this.container._disabled = false;
			}
			else
			{
				EMRenderSupport.Add(this);
			}
		}

		void OnDisable()
		{
			if (Application.isPlaying)
			{
				if (this.container != null)
					this.container._disabled = true;
			}
			else
				EMRenderSupport.Remove(this);
		}

		void OnGUI()
		{
			if (!Application.isPlaying)
				EM_BeforeUpdate();
		}

		void Start()
		{
			useGUILayout = false;

			if (!_created && Application.isPlaying)
				CreateUI();
		}

		void OnDestroy()
		{
			if (Application.isPlaying)
			{
				if (_ui != null)
				{
					_ui.Dispose();
					_ui = null;
				}

				container.Dispose();
				container = null;
			}
			else
			{
				EMRenderSupport.Remove(this);
			}

			DestroyTexture();
		}

		void CreateContainer()
		{
			this.container = new Container("UIPainter");
			this.container.renderMode = RenderMode.WorldSpace;
			this.container.renderCamera = renderCamera;
			this.container.touchable = !touchDisabled;
			this.container.fairyBatching = fairyBatching;
			this.container._panelOrder = sortingOrder;
			this.container.hitArea = new MeshColliderHitTest(this.gameObject.GetComponent<MeshCollider>());
			SetSortingOrder(this.sortingOrder, true);
			this.container.layer = CaptureCamera.hiddenLayer;
		}

		/// <summary>
		/// Change the sorting order of the panel in runtime.
		/// </summary>
		/// <param name="value">sorting order value</param>
		/// <param name="apply">false if you dont want the default sorting behavior.</param>
		public void SetSortingOrder(int value, bool apply)
		{
			this.sortingOrder = value;
			container._panelOrder = value;

			if (apply)
				Stage.inst.ApplyPanelOrder(container);
		}

		/// <summary>
		/// 
		/// </summary>
		public GComponent ui
		{
			get
			{
				if (!_created && Application.isPlaying)
					CreateUI();

				return _ui;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public void CreateUI()
		{
			if (_ui != null)
			{
				_ui.Dispose();
				_ui = null;
				DestroyTexture();
			}

			_created = true;

			if (string.IsNullOrEmpty(packageName) || string.IsNullOrEmpty(componentName))
				return;

			_ui = (GComponent)UIPackage.CreateObject(packageName, componentName);
			if (_ui != null)
			{
				this.container.AddChild(_ui.displayObject);
				((MeshColliderHitTest)this.container.hitArea).SetArea(0, 0, _ui.width, _ui.height);
				this.container.size = _ui.size;
				_texture = CaptureCamera.CreateRenderTexture(Mathf.RoundToInt(_ui.width), Mathf.RoundToInt(_ui.height), UIConfig.depthSupportForPaintingMode);
				_renderer = this.GetComponent<Renderer>();
				if (_renderer != null)
				{
					_renderer.sharedMaterial.mainTexture = _texture;
					_captureDelegate = Capture;
					if (_renderer.sharedMaterial.renderQueue == 3000) //Set in transpare queue only
					{
						this.container.onUpdate = () =>
						{
							UpdateContext.OnEnd += _captureDelegate;
						};
					}
				}
			}
			else
				Debug.LogError("Create " + componentName + "@" + packageName + " failed!");
		}

		void Capture()
		{
			CaptureCamera.Capture(this.container, _texture, Vector2.zero);
			if (_renderer != null)
				_renderer.sortingOrder = container.renderingOrder;
		}

		void DestroyTexture()
		{
			if (_texture != null)
			{
				if (Application.isPlaying)
					RenderTexture.Destroy(_texture);
				else
					RenderTexture.DestroyImmediate(_texture);
				_texture = null;
			}
		}

		#region edit mode functions

		void CaptureInEditMode()
		{
			if (!EMRenderSupport.packageListReady || UIPackage.GetByName(packageName) == null)
				return;

			_captured = true;

			DisplayOptions.SetEditModeHideFlags();
			GComponent view = (GComponent)UIPackage.CreateObject(packageName, componentName);

			if (view != null)
			{
				DestroyTexture();

				_texture = CaptureCamera.CreateRenderTexture(Mathf.RoundToInt(view.width), Mathf.RoundToInt(view.height), false);

				Container root = (Container)view.displayObject;
				root.layer = CaptureCamera.layer;
				root.SetChildrenLayer(CaptureCamera.layer);
				root.gameObject.hideFlags = HideFlags.None;
				root.gameObject.SetActive(true);

				GameObject cameraObject = new GameObject("Temp Capture Camera");
				Camera camera = cameraObject.AddComponent<Camera>();
				camera.depth = 0;
				camera.cullingMask = 1 << CaptureCamera.layer;
				camera.clearFlags = CameraClearFlags.Depth;
				camera.orthographic = true;
				camera.nearClipPlane = -30;
				camera.farClipPlane = 30;
				camera.enabled = false;
				camera.targetTexture = _texture;

				float halfHeight = (float)_texture.height / 2;
				camera.orthographicSize = halfHeight;
				cameraObject.transform.localPosition = root.cachedTransform.TransformPoint(halfHeight * camera.aspect, -halfHeight, 0);

				UpdateContext context = new UpdateContext();
				//run two times
				context.Begin();
				view.displayObject.Update(context);
				context.End();

				context.Begin();
				view.displayObject.Update(context);
				context.End();

				RenderTexture old = RenderTexture.active;
				RenderTexture.active = _texture;
				GL.Clear(true, true, Color.clear);
				camera.Render();
				RenderTexture.active = old;

				camera.targetTexture = null;
				view.Dispose();
				GameObject.DestroyImmediate(cameraObject);

				if (_renderer != null)
					_renderer.sharedMaterial.mainTexture = _texture;
			}
		}

		public void ApplyModifiedProperties(bool sortingOrderChanged)
		{
			if (sortingOrderChanged)
			{
				if (Application.isPlaying)
					SetSortingOrder(sortingOrder, true);
				else
					EMRenderSupport.orderChanged = true;
			}
		}

		public void OnUpdateSource(object[] data)
		{
			if (Application.isPlaying)
				return;

			this.packageName = (string)data[0];
			this.packagePath = (string)data[1];
			this.componentName = (string)data[2];

			if ((bool)data[3])
				_captured = false;
		}

		public int EM_sortingOrder
		{
			get { return sortingOrder; }
		}

		public void EM_BeforeUpdate()
		{
			if (_renderer == null)
				_renderer = this.GetComponent<Renderer>();
			if (_renderer != null && _renderer.sharedMaterial.mainTexture != _texture)
				_renderer.sharedMaterial.mainTexture = _texture;

			if (packageName != null && componentName != null && !_captured)
				CaptureInEditMode();
		}

		public void EM_Update(UpdateContext context)
		{
			if (_renderer != null)
				_renderer.sortingOrder = context.renderingOrder++;
		}

		public void EM_Reload()
		{
			_captured = false;
		}

		#endregion
	}
}
